package com.springboot.tools.toolkit;

import java.net.NetworkInterface;
import java.util.Enumeration;
import java.util.Random;

import org.apache.ibatis.logging.Log;
import org.apache.ibatis.logging.LogFactory;

/**
 * 
 * <p>
 * �ڷֲ�ʽϵͳ�У���Ҫ����ȫ��UID�ĳ��ϻ��ǱȽ϶�ģ�twitter��snowflake�������������
 * ʵ��Ҳ���Ǻܼ򵥵ģ���ȥ������Ϣ�����Ĵ�����Ǻ��뼶ʱ��41λ+����ID 10λ+����������12λ��
 * ����Ŀ��ַΪ��https://github.com/twitter/snowflake����Scalaʵ�ֵġ�
 * python�������Դ��Ŀhttps://github.com/erans/pysnowflake��
 * </p>
 * 
 * @author hubin
 * @Date 2016-01-22
 */

public class IdWorker
{
    // ���ݻ��������ṩ
    private final long workerId;
    // ��������ʹʱ���С�����ɵ���λ����С��һ��ȷ�����ܶ�
    private final static long twepoch = 1361753741828L;
    private long sequence = 0L;
    private final static long workerIdBits = 10L;
    private final static long maxWorkerId = -1L ^ -1L << workerIdBits;
    private final static long sequenceBits = 12L;
    private final static long workerIdShift = sequenceBits;
    private final static long timestampLeftShift = sequenceBits + workerIdBits;
    private final static long sequenceMask = -1 ^ -1L << sequenceBits;
    private long lastTimestamp = -1L;

    // �����ͽ��̵Ļ�����
    private static IdWorker worker = new IdWorker();
    private static final Log logger = LogFactory.getLog(IdWorker.class);
    // �����ͽ��̵Ļ�����
    private static final int _gemachine;

    static
    {
        try
        {
            // build a 2-bye machine piece based on NICS info
            int machinePiece;
            {
                try
                {
                    StringBuilder sb = new StringBuilder();
                    Enumeration<NetworkInterface> e = NetworkInterface
                            .getNetworkInterfaces();
                    while (e.hasMoreElements())
                    {
                        NetworkInterface ni = e.nextElement();
                        sb.append(ni.toString());
                    }
                    machinePiece = sb.toString().hashCode() << 16;
                }
                catch (Throwable e)
                {
                    // exception sometimes happens with IBM JVM, use random
                    logger.error("IdWorker error. ", e);
                    machinePiece = new Random().nextInt() << 16;
                }
                logger.debug("machine piece post : "
                        + Integer.toHexString(machinePiece));
            }

            // add a 2 byte process piece. It must represent not only the JVM
            // but the class loader.
            // Since static var belong to class loader there could be collisions
            // otherwise
            final int processPiece;
            {
                int processId = new Random().nextInt();
                try
                {
                    processId = java.lang.management.ManagementFactory
                            .getRuntimeMXBean().getName().hashCode();

                }
                catch (Throwable t)
                {
                }
                ClassLoader loader = IdWorker.class.getClassLoader();
                int loaderId = loader != null ? System.identityHashCode(loader)
                        : 0;
                StringBuilder sb = new StringBuilder();
                sb.append(Integer.toHexString(processId));
                sb.append(Integer.toHexString(loaderId));
                processPiece = sb.toString().hashCode() & 0xFFFF;
                logger.debug("process piece: "
                        + Integer.toHexString(processPiece));

            }
            _gemachine = machinePiece | processPiece;
            logger.debug("machine :" + Integer.toHexString(_gemachine));

        }
        catch (Exception e)
        {
            throw new RuntimeException(e);
        }
    }

    public IdWorker()
    {
        workerId = _gemachine % (IdWorker.maxWorkerId + 1);
    }

    public static long getId()
    {
        return worker.nextId();
    }

    public synchronized long nextId()
    {
        long timestamp = timeGen();

        if (lastTimestamp == timestamp)
        {
            sequence = sequence + 1 & IdWorker.sequenceMask;
            if (sequence == 0)
            {
                // System.out.println("###########" + sequenceMask);//�ȴ���һ����
                timestamp = tilNextMillis(lastTimestamp);
            }
        }
        else
        {
            sequence = 0;
        }
        if (timestamp < lastTimestamp)
        {
            try
            {
                throw new Exception(
                        String.format(
                                "Clock moved backwards.  Refusing to generate id for %d milliseconds",
                                lastTimestamp - timestamp));
            }
            catch (Exception e)
            {
                logger.error(" IdWorker error. ", e);
            }
        }

        lastTimestamp = timestamp;
        return timestamp - twepoch << timestampLeftShift
                | workerId << IdWorker.workerIdShift | sequence;
    }

    private long tilNextMillis(final long lastTimestamp1)
    {
        long timestamp = timeGen();
        while (timestamp <= lastTimestamp1)
        {
            timestamp = timeGen();
        }
        return timestamp;
    }

    private long timeGen()
    {
        return System.currentTimeMillis();
    }
}
